﻿// Inner Fire 游戏引擎库
// Armature - 骨骼
//
// Copyright (c) 2025 by 尔西大帝. All rights reserved.
//
// 所属层级：应用层(4)
// 用于提取模型中的骨骼数据。方便动画和模型的统一管理
//
// Author: 尔西大帝
// Email: 2670613871@qq.com
// Created: 2025-03-13

#include "armature.h"
#include "klog.h"
#include <game/animation/avatar.h>
#include <global_switch.h>
#include <graphics/imath.h>
#include <kstring.h>

using namespace DirectX;

namespace ifire::game {
static bool print_anime_calc = false;

Armature::Armature(
    const aiScene* scene, bool is_simplify_node, bool convert_units_)
    : avatar_(nullptr), is_simplify_node_(is_simplify_node) {

  // 这里是这样的：在Blender中，一个对象导出的时候，其中心未必会在世界原点。
  // 然后我们需要将其变换到世界原点之后，再进行动画的计算，然后再将其变回原来的位置。
  // 这就是为什么需要m_GlobalInverseTransform的原因。
  // 但是我们在项目中使用的骨骼动画都是在原点。所以这个项目几乎是没有意义的，
  // 反而会使项目变得更加复杂。这里只是留着这个做为一种提醒，在特定的时候也许拥有价值。
  global_inverse_transform_ =
      imath::ConvertMatrix(scene->mRootNode->mTransformation);
  XMStoreFloat4x4(&global_inverse_transform_,
      XMMatrixInverse(nullptr, XMLoadFloat4x4(&global_inverse_transform_)));

  // 复制Scene中的骨架
  node_ = std::make_unique<ArmatureTree>(scene->mRootNode, convert_units_);

  // 设置一个0号骨骼。这个骨骼的Transform为固定项目，因为在权重的计算中，没有权重的骨骼的ID设置为0。
  // 与ID0的骨骼计算的时候，永远设置为单位矩阵。
  AddBone("UNNAMED0", IDENTITY_4X4);

  // 为每一个项目定名（这里可以使用string_view，因为项目在root_node中常驻）
  // StackBone(root_node_);
}

Armature::Armature(ikit::CachedFile& f) {
  global_inverse_transform_ = IDENTITY_4X4;
  // 读取Bone的信息
  int bone_count = f.readInt();
  bones_.reserve(bone_count);
  char buffer[64];
  for (int i = 0; i < bone_count; i++) {
    // 读取信息
    int id = f.readInt();
    ikit::sstr name = f.readString(buffer);
    XMFLOAT4X4 mat;
    f.readBytes(&mat, sizeof(mat));

    // 加入Bone
    bones_.emplace_back(id, name, mat);

    //  配置到map中
    bone_map_[name.uid()] = id;
  }

  // 读取Armature信息
  node_ = std::make_unique<ArmatureTree>(f);
}

void Armature::Save(ikit::CachedFile& f) {
  // Bones的信息
  f.writeInt(static_cast<int>(bones_.size()));

  for (auto& i : bones_) {
    f.writeInt(i.id);
    f.writeString(i.name.c_str());
    f.writeBytes(&i.offset_mat, sizeof(i.offset_mat));
  }
  // Nodes的信息
  node_->Save(f);
}

int Armature::GetBoneId(ikit::sstr name) const {
  auto it = bone_map_.find(name.uid());
  if (it != bone_map_.end()) {
    return it->second;
  } else {
    return -1;
  }
}

BoneInfo& Armature::GetBone(int bone_id) { return bones_[bone_id]; }

BoneInfo& Armature::AddBone(ikit::sstr name, IMat4 offset_matrix) {
  // 当前的BONEID就是当前的大小
  int bone_id = static_cast<int>(bones_.size());
  //  配置到map中
  bone_map_[name.uid()] = bone_id;
  // 插入项目
  return bones_.emplace_back(bone_id, name, offset_matrix);
}

void Armature::PrintBoneData(const char* file_name) {
  ikit::file_printer f(file_name);
  PrintBoneHierarchy(ID_MAT, f, node_->Root());
}

void Armature::PrintBoneHierarchy(
    FXMMATRIX parent_mat, ikit::file_printer& f, const ArmatureNode* node) {
  // 得到这个项目的变换矩阵
  auto node_trans_mat = node->transformation;
  // 划重点，M3D中的乘法和教程里面也是相反的。
  auto current_mat = XMMatrixMultiply(node_trans_mat, parent_mat);

  if (node->bone_id != -1) {

    auto& bone = bones_[node->bone_id];

    auto offset = XMLoadFloat4x4(&bone.offset_mat);
    auto final_mat = XMMatrixMultiply(offset, current_mat);

    Trans3 t;

    t = node->transformation;
    t.Print(f, "NODE %s:", node->name.c_str());

    t = current_mat;
    t.Print(f, "CURR %s:", node->name.c_str());

    t = bone.offset_mat;

    t.Print(f, "BONE %s:", bone.name.c_str());
    t = final_mat;
    t.Print(f, "FINL %s:", bone.name.c_str());

    f.print("\n");
  }

  // 继续子项目的配置
  for (size_t i = 0; i < node->children.size(); i++) {
    PrintBoneHierarchy(current_mat, f, node->children[i]);
  }
}

void Armature::AttachBonesToNodes() { AttachBone(node_->Root()); }

void Armature::AttachBone(ArmatureNode* node) {
  // 因为节点名称和骨骼名称一致，所以直接通过名称赋值。
  // 当返回-1的时候表示没有指定的骨骼，但这也不需要特殊处理，正好适合。
  node->bone_id = GetBoneId(node->name);

  // 检查子项目
  for (size_t i = 0; i < node->children.size(); i++) {
    AttachBone(node->children[i]);
  }
}

void Armature::GetFinal(
    const MatrixList& transforms, eastl::vector<IMat4>& finals) {
  for (size_t i = 1; i < transforms.size(); i++) {
    auto offset = XMLoadFloat4x4(&bones_[i].offset_mat);
    auto final_mat = offset * transforms[i];
    XMStoreFloat4x4(&finals[i], XMMatrixTranspose(final_mat));
  }
}

void Armature::PrintNode(bool with_bone) const {
  KLOG("\n---------------------------------------------");
  KLOG("Print Armature Node:");
  // 全局逆向矩阵
  KLOG("-- Global Invert --");
  ikit::print_mat_break(global_inverse_transform_);
  // 打印通用骨架
  if (avatar_) {
    avatar_->Print();
  }

  KLOG("---------------------------------------------");

  //
  ikit::reset_tab();
  PrintNodeHierarchy(Root(), with_bone);

  KLOG("\n-- END --\n");
}

void Armature::PrintNodeHierarchy(
    const ArmatureNode* node, bool with_bone) const {
  ikit::print_tab();
  if (node->bone_id != -1) {
    auto& b = bones_[node->bone_id];
    ikit::log("-- %s [%d]%s --", node->name.gbk().c_str(), node->bone_id,
        b.name.gbk().c_str());
  } else {
    ikit::log("-- %s --", node->name.gbk().c_str());
  }
  // ikit::print_tab("[NodeTrans   ]\t");
  // ikit::print_mat_break(node->transformation, false);

  if (with_bone && node->bone_id != -1) {
    auto& b = bones_[node->bone_id];
    // ikit::print_tab("[OffsetMatrix]\t");
    // ikit::print_mat_break(b.offset_mat, false);
  }

  // 继续子项目的配置
  ikit::add_tab();
  for (size_t i = 0; i < node->children.size(); i++) {
    PrintNodeHierarchy(node->children[i], with_bone);
  }
  ikit::back_tab();
}

void Armature::SetRootMotionNode(ikit::sstr name) {
  node_->SetRootMotionNode(name);
}

void Armature::SetByAvatar(Avatar* avatar) {
  avatar_ = avatar;
  node_->SetByAvatar(avatar);
}

const Avatar* Armature::GetAvatar() const { return avatar_; }

} // namespace ifire::game